use super::{
    err, err_value, get_cfg, json, ok,
    pbase::{PairBase, SData},
    save_cfg, save_data, throw_err, Base, Client, ECode, Lock, MapSv, RNull, Resp, TBase, VBase,
    VMap, Value, VecV, SS,
};
use std::collections::{HashMap, HashSet};
use tracing::{instrument, warn};

pub struct Select;

impl Base for Select {}
impl TBase for Select {}

impl Select {
    pub async fn do_cmd(&self, para: &Value, cmd: &str, client: &Client, lock: Lock) -> Resp {
        let data = para.ckobj()?;
        let line = data.ck_str("line")?;

        match cmd {
            "pair" => self.do_pair(line, data, client, lock).await,
            _ => throw_err!(ECode::NoServe, "no select serve"),
        }
    }

    async fn add_task(&self, line: &str, tsn: &str, data: &Value, client: &Client) -> RNull {
        let now = self.cur_time("%Y%m%d");
        let task_sn = format!("sel_{tsn}_{now}");

        let cmd = json!({
            "line": line,
            "k": &task_sn,
        });

        if let Err(_) = client.cfgdb(&cmd, "task/get").await {
            let cmd = json!({
                "line": line,
                "v": {
                    "sn": task_sn,
                    "pstart": base::cur_time("%Y-%m-%d %H:%M:%S"),
                    "pend": self.next_cur_time_day(1, "%Y-%m-%d %H:%M:%S")?,
                    "pnum": 20000,
                    "tech": tsn,
                    "state": "open",
                }
            });

            client.cfgdb(&cmd, "task/add").await?;
        }

        let cmd = json!({
            "line": "1",
            "k": task_sn,
            "v": [data],
        });

        client.cfgdb(&cmd, "psn/add").await?;

        Ok(())
    }

    async fn do_pair(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let sel_sn = data.ck_str("sel")?;

        let cmd = json!({ "line": line });

        let cfg = client
            .cfgdb(&cmd, &format!("file/read/select_{sel_sn}"))
            .await?;
        let cfg = cfg.ck_obj("data")?;

        let pair = cfg.ck_obj("pair")?;

        let ball_num = pair.ck_i64("num")?;
        let ball_size = pair.ck_f64("ball")?;
        let mt = pair.ck_str("mt")?;

        let mut pbase = PairBase::new();

        pbase.para(line, &pair)?;
        pbase.get_tech(client, false).await?;

        let ball = pbase.load_ball(client).await?;

        pbase.load_select(cfg, client).await?;

        let mut out_val = Vec::new();
        let mut ret = Vec::new();

        warn!("step: start pair");

        let tsn = pbase.tsn();
        let mut all_log = Vec::new();

        warn!("step: start pair");

        // outer psn is the task's psn
        for (out_psn, out_data) in pbase.out_tech.iter() {
            let out = match pbase.out_val(out_data) {
                Some(v) => v,
                None => {
                    all_log.push(format!("缺失外圈数据: {:?}", out_psn));
                    continue;
                }
            };

            if !pbase.ck_outdia(&out, &mut all_log) {
                continue;
            }

            let mut log = HashMap::new();

            // scan inner psn for pair
            for (in_psn, in_data) in pbase.in_tech.iter() {
                let inval = match self.get_pair_val(in_data) {
                    Ok(v) => v,
                    Err(_) => {
                        continue;
                    }
                };

                if !pbase.ck_india(&inval, &mut log) {
                    continue;
                }

                if !pbase.ck_dia(&out, &inval, &mut log) {
                    continue;
                }

                if !pbase.ck_width(&out, &inval, &mut log) {
                    continue;
                }

                let ball_batch = match pbase.ck_ball(&ball, &out, &inval, &mut log) {
                    Some(v) => v,
                    None => {
                        continue;
                    }
                };

                let f = &ball_batch[0];

                ret.push(json!({
                    "sn": out_psn,
                    "insn": in_psn,
                    "boxin": in_data.ck_str("box")?,
                    "posin": in_data.ck_str("pos")?,
                    "boxout": out_data.ck_str("box")?,
                    "posout": out_data.ck_str("pos")?,
                }));

                out_val.push(json!({
                    "sn": out_psn,
                    "insn": in_psn,
                    "outdia": self.to_m(out.dia),
                    "outdt": self.to_m(out.dt),
                    "outwidth": self.to_m(out.width),
                    "india": self.to_m(inval.dia),
                    "indt": self.to_m(inval.dt),
                    "inwidth": self.to_m(inval.width),
                    "bsn": f.ck_str("sn")?,
                    "ball": ball_size,
                    "num": ball_num,
                    "std": f.ck_f64("std")?,
                    "sub": f.ck_f64("sub")?,
                    "mt": mt,
                    "dw": self.to_m(out.dt - inval.dt - 2.0 * dw_val),
                    "tech": tsn,
                }));

                pbase.in_tech.remove(in_psn);

                log.clear();
                break;
            } //for

            if !log.is_empty() {
                for (_, v) in log.iter() {
                    all_log.push(v.clone());
                }
            }
        } //for

        for d in out_val.iter() {
            self.add_task(line, tsn, d, client).await?;

            let insn = d.ck_str("insn")?;
            let sn = d.ck_str("sn")?;

            let cmd = json!({ "line": line, "k": insn });
            let _ = client.cfgdb(&cmd, "inner/rm").await;

            let cmd = json!({ "line": line, "k": sn });
            let _ = client.cfgdb(&cmd, "outer/rm").await;
        }

        pbase.set_select_box(&ret, client).await;

        ok!(ret)
    }

    fn get_pair_val(&self, data: &Value) -> Result<SData, Value> {
        let data = data.ckobj()?;

        let (dia, dt, width) = self.get_val(data)?;

        let out = SData::new(
            0.0,
            0.0,
            0.0,
            dia,
            dt,
            width,
            "".to_string(),
            "".to_string(),
        );

        Ok(out)
    }

    fn get_val(&self, data: &MapSv) -> Result<(f64, f64, f64), Value> {
        let dia = data.ck_f64("dia")?; // D
        let dt = data.ck_f64("dt")?; // De
        let width = data.ck_f64("width")?; // C

        Ok((self.round_dia(dia / 1000.0), dt / 1000.0, width / 1000.0))
    }
}
